第二章: 數據繪圖文法
2024 三月 01
#整體設定,含載入套件
source("https://raw.githubusercontent.com/ChungPingCheng/R4BS2/main/R4BS_setup2.R")
資料與管理
#讀檔案
dta <- read.csv(file = "../Data/HEXACO_HS.csv",
header = TRUE, stringsAsFactors = TRUE)
#檢視資料結構
#程式報表2.1
str(dta)
'data.frame': 897 obs. of 13 variables:
$ 性別 : Factor w/ 2 levels "女","男": 2 1 2 2 1 1 2 2 1 1 ...
$ 父親教育程度: Factor w/ 5 levels "大學或專科","小學或不識字",..: 5 4 5 4 4 4 2 1 3 1 ...
$ 母親教育程度: Factor w/ 5 levels "大學或專科","小學或不識字",..: 5 5 4 4 4 4 2 4 1 4 ...
$ 誠實.謙遜 : int 48 60 53 48 53 66 55 58 51 58 ...
$ 情緒性 : int 54 43 50 52 45 68 52 44 57 62 ...
$ 外向性 : int 44 39 48 46 51 60 38 55 54 67 ...
$ 和悅性 : int 50 55 47 50 51 48 55 54 51 61 ...
$ 嚴謹性 : int 43 59 52 46 48 51 43 43 50 53 ...
$ 開放性 : int 44 57 44 49 45 51 38 50 50 52 ...
$ 攻擊行為 : int 13 2 1 12 21 1 4 2 8 4 ...
$ 焦慮.憂鬱 : int 11 2 0 9 14 6 5 1 6 4 ...
$ 違反規定 : int 7 1 0 9 12 0 1 3 4 3 ...
$ 社會退縮 : int 9 4 0 5 12 2 4 2 5 0 ...
#設定類別變項的順序
dta <- dta |>
dplyr::mutate(母親教育程度 = forcats::fct_relevel(母親教育程度,
c("小學或不識字",
"國中",
"高中",
"大學或專科",
"研究所以上")),
父親教育程度 = forcats::fct_relevel(父親教育程度,
c("小學或不識字",
"國中",
"高中",
"大學或專科",
"研究所以上")))
畫圖
#瞭解 ggplot 啟動後的設定規劃
#程式報表2.2
ggplot() |> attributes()
$names
[1] "data" "layers" "scales" "mapping" "theme"
[6] "coordinates" "facet" "plot_env" "labels"
$class
[1] "gg" "ggplot"
#ggplot 是一個一個圖層(layer)疊上去
#底下以散佈圖為例,呈現每一步驟的結果
#第一步驟設定圖的框架,注意圖的X軸與Y軸
#圖2.1
g0 <- ggplot(data = dta,
aes(x=社會退縮, y=攻擊行為))
g0
#在前一步驟結果(圖的框架)上加入點
#圖2.2
g1 <- g0 + geom_point(alpha=.2)
g1
#在前一步驟結果上加入橢圓(資料的95%區間)與局部迴歸線
#圖2.3
g2 <- g1 + stat_smooth(method='lm',
formula = y ~ x,
se=FALSE,
linewidth = 0.5)
g2
#在前一步驟結果上要求以變項(性別)區分顏色
#圖2.4
g3 <- g2 + aes(color=性別)
g3
#在前一步驟結果上設定 x 軸與 y 軸刻度
#圖2.5
g4 <- g3 +
scale_y_continuous(breaks=seq(0, 25, by=5)) +
scale_x_continuous(breaks=seq(0, 12, by=2))
g4
#在前一步驟結果上要求以變項(父母教育)分面(facet)
#圖2.6
g5 <- g4 + facet_wrap(vars(母親教育程度),nrow=1)
g5
#在前一步驟結果上加上X軸、Y軸的標示,以及整個圖形的標題
#圖2.7
g6 <- g5 + labs(x='社會退縮分數',
y='攻擊行為分數',
title='散布圖:攻擊行為與社會退縮')
g6
#在前一步驟結果上改變主題,並要求圖示位置
#圖2.8
g7 <- g6 + theme_minimal() +
theme(legend.position='top')
g7
#前面步驟可以一次執行
ggplot(data = dta, aes(x=社會退縮, y=攻擊行為)) +
geom_point(alpha=.2) +
stat_smooth(method='lm', formula = y ~ x,
se=FALSE, linewidth = 0.5)+
aes(color=性別) +
scale_y_continuous(breaks=seq(0, 25, by=5)) +
scale_x_continuous(breaks=seq(0, 12, by=2)) +
facet_wrap(vars(母親教育程度),nrow=1)+
labs(x='社會退縮分數', y='攻擊行為分數',
title='散布圖:攻擊行為與社會退縮') +
theme_minimal() +
theme(legend.position='top')
#設定後面的 ggplot 繪圖以 theme_minimal 為預設值
ggplot2::theme_set(theme_minimal())
繪製統計摘要
#以 ggplot 直接繪製統計摘要結果(連續資料平均數與標準誤)
#圖2.9
ggplot(data=dta,
aes(x=母親教育程度, y=誠實.謙遜, color=性別)) +
stat_summary(fun.data = "mean_cl_boot",
position=position_dodge(.2)) +
stat_summary(aes(group=性別), fun = mean,
geom="line",
position=position_dodge(.2))+
scale_color_grey(end=.7)+
labs(y='誠實.謙遜平均分數',
x='母親教育程度',
title='不同性別的誠實.謙遜平均跟母親教育程度的關係',
caption="來源: 許功餘")+
theme(legend.position='top')
#程式報表2.3
dta |>
dplyr::group_by(性別, 母親教育程度) |>
dplyr::reframe( 誠謙平均 = mean(誠實.謙遜),
誠謙標準誤 = sd(誠實.謙遜)/sqrt(n()),
誠謙平均下界 = 誠謙平均 - 1.96*誠謙標準誤,
誠謙平均上界 = 誠謙平均 + 1.96*誠謙標準誤)
|
性別
|
母親教育程度
|
誠謙平均
|
誠謙標準誤
|
誠謙平均下界
|
誠謙平均上界
|
|
女
|
小學或不識字
|
59.13
|
1.9299
|
55.35
|
62.91
|
|
女
|
國中
|
59.18
|
0.8071
|
57.59
|
60.76
|
|
女
|
高中
|
57.33
|
0.4803
|
56.38
|
58.27
|
|
女
|
大學或專科
|
57.61
|
0.7780
|
56.08
|
59.13
|
|
女
|
研究所以上
|
58.00
|
3.2146
|
51.70
|
64.30
|
|
男
|
小學或不識字
|
55.40
|
1.4261
|
52.60
|
58.20
|
|
男
|
國中
|
57.05
|
0.9345
|
55.22
|
58.89
|
|
男
|
高中
|
53.95
|
0.6108
|
52.75
|
55.15
|
|
男
|
大學或專科
|
51.74
|
0.8171
|
50.13
|
53.34
|
|
男
|
研究所以上
|
52.38
|
1.7314
|
48.98
|
55.77
|
#以 ggplot 直接繪製統計摘要結果(類別資料的百分比)
#圖2.10
ggplot(dta,
aes(x=母親教育程度, group=父親教育程度)) +
geom_bar(aes(y=after_stat(prop),
fill = factor(after_stat(x))),
width = .2) +
scale_y_continuous(labels=scales::percent) +
scale_fill_grey()+
coord_flip()+
labs(y="百分比",
title="門當戶對:父母親教育程度") +
facet_wrap(vars(父親教育程度), ncol=1)+
theme(legend.position="none")
#程式報表2.4
dta |>
dplyr::select(母親教育程度, 父親教育程度) |>
gtsummary::tbl_cross(percent=c("column"))
|
父親教育程度
|
Total |
| 小學或不識字 |
國中 |
高中 |
大學或專科 |
研究所以上 |
| 母親教育程度 |
|
|
|
|
|
|
| 小學或不識字 |
13 (34%) |
16 (7.9%) |
13 (3.3%) |
1 (0.4%) |
0 (0%) |
43 (4.8%) |
| 國中 |
15 (39%) |
95 (47%) |
68 (17%) |
11 (4.7%) |
0 (0%) |
189 (21%) |
| 高中 |
8 (21%) |
80 (39%) |
277 (70%) |
72 (31%) |
1 (4.5%) |
438 (49%) |
| 大學或專科 |
2 (5.3%) |
12 (5.9%) |
39 (9.8%) |
148 (63%) |
15 (68%) |
216 (24%) |
| 研究所以上 |
0 (0%) |
0 (0%) |
1 (0.3%) |
4 (1.7%) |
6 (27%) |
11 (1.2%) |
| Total |
38 (100%) |
203 (100%) |
398 (100%) |
236 (100%) |
22 (100%) |
897 (100%) |
#以 ggplot 直接繪製統計摘要結果(連續資料平均數與標準誤)
#圖2.11
ggplot(data=dta,
aes(x=母親教育程度, y=誠實.謙遜)) +
stat_summary(fun.data = "mean_cl_boot") +
scale_color_grey(end=.7)+
facet_wrap(vars(父親教育程度), nrow=1)+
labs(y='誠實.謙遜平均分數',
x='母親教育程度',
title='不同父親教育程度的誠實.謙遜平均跟母親教育程度的關係',
caption="來源: 許功餘")+
theme(legend.position='top',
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
多變量圖形
#多變量圖形
#圖2.12,dpi=300
dta |> tidyr::pivot_longer(cols=4:9,
names_to = '人格維度',
values_to = '分數') |>
ggplot()+
aes(x=違反規定, y=分數, color=性別)+
geom_point(size=rel(.5))+
stat_smooth(method='lm',
formula = y ~ x,
se=F, linewidth=.5)+
facet_grid(人格維度 ~ 母親教育程度) +
scale_color_grey(start=.1, end=.6)+
labs(x="違反規定分數",
y="人格維度分數")+
theme(legend.position='top')
延伸
#同時繪製兩個變項的散布圖,以及各自的邊際分布
#圖2.13
p <- ggplot(dta,
aes(x=誠實.謙遜, y=違反規定)) +
geom_point(shape=21, alpha=.5) +
stat_ellipse() +
geom_vline(xintercept=mean(dta$誠實.謙遜), col="gray") +
geom_hline(yintercept=mean(dta$違反規定), col="gray") +
stat_smooth(method="lm",
formula = y ~ x,
linewidth=.7,
linetype="dotted",
se=FALSE,
alpha=.5,
col=1) +
labs(y="違反規定分數",
x="誠實.謙遜分數")
pacman::p_load(ggExtra, KernSmooth)
ggMarginal(p,
type="histogram",
xparams=list(binwidth=dpih(dta$誠實.謙遜),
fill="gray90"),
yparams=list(binwidth=dpih(dta$違反規定),
fill="gray90"))
繪製模型分析結果
#以 tidy 方式整理後繪製
#圖2.14
lm(誠實.謙遜 ~ 性別 + 母親教育程度 + 性別:母親教育程度, data=dta) |>
broom::tidy(conf.int=TRUE) |>
dplyr::slice(-1) |>
ggplot() +
aes(estimate, term, xmin = conf.low, xmax = conf.high, height = 0) +
geom_errorbarh()+
geom_point() +
geom_vline(xintercept = 0, linetype='dotted', col='gray')
#繪製模型分析所得參數
#以套件GGally整理後繪製
#圖2.15
lm(誠實.謙遜 ~ 性別 + 母親教育程度 + 性別:母親教育程度, data=dta) %>%
GGally::ggcoef(., exclude_intercept=TRUE,
sort=NULL,
na.rm=TRUE) +
labs(x="估計值",
y="變項")
#繪圖檢視模型與資料的配適性
#這邊先做性別與父母教育對數學成績的二因子 ANOVA
#程式報表2.5
lm(誠實.謙遜 ~ 性別*母親教育程度, data=dta) |> anova()
|
|
Df
|
Sum Sq
|
Mean Sq
|
F value
|
Pr(\>F)
|
|
性別
|
1
|
3422.0
|
3422.01
|
50.432
|
0.0000
|
|
母親教育程度
|
4
|
1472.6
|
368.14
|
5.426
|
0.0003
|
|
性別:母親教育程度
|
4
|
382.2
|
95.55
|
1.408
|
0.2293
|
|
Residuals
|
887
|
60186.1
|
67.85
|
NA
|
NA
|
#繪製模型分析所得參數,並與實際資料對照
#先彙整資料
#程式報表2.6
ef_m1 <- lm(誠實.謙遜 ~ 性別 + 母親教育程度, data=dta) %>%
ggeffects::ggpredict(., terms=c("母親教育程度", "性別")) |>
as.data.frame()
ef_m1
|
x
|
predicted
|
std.error
|
conf.low
|
conf.high
|
group
|
|
小學或不識字
|
59.13
|
1.2835
|
56.61
|
61.65
|
女
|
|
小學或不識字
|
55.40
|
1.2918
|
52.86
|
57.93
|
男
|
|
國中
|
59.96
|
0.6576
|
58.67
|
61.25
|
女
|
|
國中
|
56.23
|
0.6637
|
54.92
|
57.53
|
男
|
|
高中
|
57.50
|
0.4759
|
56.56
|
58.43
|
女
|
|
高中
|
53.76
|
0.4875
|
52.81
|
54.72
|
男
|
|
大學或專科
|
56.33
|
0.6513
|
55.05
|
57.61
|
女
|
|
大學或專科
|
52.60
|
0.6038
|
51.41
|
53.78
|
男
|
|
研究所以上
|
56.63
|
2.5184
|
51.68
|
61.57
|
女
|
|
研究所以上
|
52.89
|
2.4905
|
48.00
|
57.78
|
男
|
#繪製模型分析所得參數,並與實際資料對照
#注意 y 被設定四次
#圖2.16
ggplot() +
stat_summary(data=dta,
aes(x=母親教育程度, y=誠實.謙遜, color=性別),
fun.data = "mean_cl_boot",
size=rel(.3),
position=position_dodge(.2))+
geom_line(data=ef_m1, aes(x=x, y=predicted,
group=group,
color=group))+
geom_line(data=ef_m1, aes(x=x, y=conf.low,
col=group, group=group),
linetype='dotted')+
geom_line(data=ef_m1, aes(x=x, y=conf.high,
col=group, group=group),
linetype='dotted') +
scale_color_grey(end=.6)+
labs(y='誠實.謙遜平均分數',
x='母親教育程度',
title='性別跟母親教育對誠實.謙遜平均的效果')+
guides(color=guide_legend(title="性別"))+
theme(legend.position='top')
References
Wickham, H. (2016). ggplot2: Elegant Graphics for Data Analysis. New
York: Springer-Verlag.